package br.com.seimos.minijava.frame;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import br.com.seimos.minijava.temp.Label;
import br.com.seimos.minijava.temp.Temp;
import br.com.seimos.minijava.tree.CALL;
import br.com.seimos.minijava.tree.Exp;
import br.com.seimos.minijava.tree.ExpList;
import br.com.seimos.minijava.tree.MOVE;
import br.com.seimos.minijava.tree.NAME;
import br.com.seimos.minijava.tree.SEQ;
import br.com.seimos.minijava.tree.Stm;
import br.com.seimos.minijava.tree.TEMP;
import br.com.seimos.minijava.utils.BoolList;

/*
 * Algumas informacoes encontradas para auxilio na construcao do frame MIPS
 * Fonte: http://www.cesarkallas.net/arquivos/faculdade/arquitetura-computadores/teoria/cap3hp.pdf
 * 32 Registradores de 32 bits($0..$31)
 * Convencao para registradores MIPS -
 *   $zero - $0 - Constante 0
 *   $v0 - $2 - Resultado e avaliacoes de expressoes - Nao preservado
 *   $v1 - $3 - Resultado e avaliacoes de expressoes - Nao preservado
 *   $a0 - $4 - Argumento - Preservado
 *   $a1 - $5 - Argumento - Preservado
 *   $a2 - $6 - Argumento - Preservado
 *   $a3 - $7 - Argumento - Preservado
 *   $t0 - $8 - Temporario - Nao Preservado
 *   $t1 - $9 - Temporario - Nao Preservado
 *   $t2 - $10 - Temporario - Nao Preservado
 *   $t3 - $11 - Temporario - Nao Preservado
 *   $t4 - $12 - Temporario - Nao Preservado
 *   $t5 - $13 - Temporario - Nao Preservado
 *   $t6 - $14 - Temporario - Nao Preservado
 *   $t7 - $15 - Temporario - Nao Preservado
 *   $s0 - $16 - Salvos - Preservado
 *   $s1 - $17 - Salvos - Preservado
 *   $s2 - $18 - Salvos - Preservado
 *   $s3 - $19 - Salvos - Preservado
 *   $s4 - $20 - Salvos - Preservado
 *   $s5 - $21 - Salvos - Preservado
 *   $s6 - $22 - Salvos - Preservado
 *   $s7 - $23 - Salvos - Preservado
 *   $t8 - $24 - Temporario - Nao Preservado
 *   $t9 - $25 - Temporario - Nao Preservado
 *   $gp - $28 - Ponteiro Global - Preservado
 *   $sp - $29 - Stack Pointer - Preservado
 *   $fp - $30 - Frame Pointer - Preservado
 *   $ra - $31 - Endereco de retorno - Preservado
 */

public class MipsFrame extends Frame {

	// Registradores descritos acima
	static final Temp ZERO = new Temp();
	static final Temp AT = new Temp();
	static final Temp V0 = new Temp();
	static final Temp V1 = new Temp();
	static final Temp A0 = new Temp();
	static final Temp A1 = new Temp();
	static final Temp A2 = new Temp();
	static final Temp A3 = new Temp();
	static final Temp T0 = new Temp();
	static final Temp T1 = new Temp();
	static final Temp T2 = new Temp();
	static final Temp T3 = new Temp();
	static final Temp T4 = new Temp();
	static final Temp T5 = new Temp();
	static final Temp T6 = new Temp();
	static final Temp T7 = new Temp();
	static final Temp S0 = new Temp();
	static final Temp S1 = new Temp();
	static final Temp S2 = new Temp();
	static final Temp S3 = new Temp();
	static final Temp S4 = new Temp();
	static final Temp S5 = new Temp();
	static final Temp S6 = new Temp();
	static final Temp S7 = new Temp();
	static final Temp T8 = new Temp();
	static final Temp T9 = new Temp();
	static final Temp K0 = new Temp();
	static final Temp K1 = new Temp();
	static final Temp GP = new Temp();
	static final Temp SP = new Temp();
	static final Temp S8 = new Temp();
	static final Temp RA = new Temp();

	private static final Temp[] specialRegs = { ZERO, AT, K0, K1, GP, SP }, //reservados para propositos especiais
			argRegs = { A0, A1, A2, A3 }, //argumentos
			calleeSaves = { RA, S0, S1, S2, S3, S4, S5, S6, S7, S8 }, //salvos pelo chamado
			callerSaves = { T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, V0, V1 }; //salvos pelo chamador

	private static final HashMap<Temp, String> tempMap = new HashMap<Temp, String>(32);
	static {
		tempMap.put(ZERO, "$0");
		tempMap.put(AT, "$at");
		tempMap.put(V0, "$v0");
		tempMap.put(V1, "$v1");
		tempMap.put(A0, "$a0");
		tempMap.put(A1, "$a1");
		tempMap.put(A2, "$a2");
		tempMap.put(A3, "$a3");
		tempMap.put(T0, "$t0");
		tempMap.put(T1, "$t1");
		tempMap.put(T2, "$t2");
		tempMap.put(T3, "$t3");
		tempMap.put(T4, "$t4");
		tempMap.put(T5, "$t5");
		tempMap.put(T6, "$t6");
		tempMap.put(T7, "$t7");
		tempMap.put(S0, "$s0");
		tempMap.put(S1, "$s1");
		tempMap.put(S2, "$s2");
		tempMap.put(S3, "$s3");
		tempMap.put(S4, "$s4");
		tempMap.put(S5, "$s5");
		tempMap.put(S6, "$s6");
		tempMap.put(S7, "$s7");
		tempMap.put(T8, "$t8");
		tempMap.put(T9, "$t9");
		tempMap.put(K0, "$k0");
		tempMap.put(K1, "$k1");
		tempMap.put(GP, "$gp");
		tempMap.put(SP, "$sp");
		tempMap.put(S8, "$fp");
		tempMap.put(RA, "$ra");
	}

	private static Temp[] registers = {};
	{
		LinkedList<Temp> l = new LinkedList<Temp>();
		addAll(l, callerSaves);
		addAll(l, calleeSaves);
		addAll(l, argRegs);
		addAll(l, specialRegs);
		registers = l.toArray(registers);
	}

	// Registradores vivos no retorno
	private static Temp[] returnSink = {};
	{
		LinkedList<Temp> l = new LinkedList<Temp>();
		l.add(V0);
		addAll(l, specialRegs);
		addAll(l, calleeSaves);
		returnSink = l.toArray(returnSink);
	}

	// Registradores salvos pelo chamador
	static Temp[] calldefs = {};
	{
		LinkedList<Temp> l = new LinkedList<Temp>();
		l.add(RA);
		addAll(l, argRegs);
		addAll(l, callerSaves);
		calldefs = l.toArray(calldefs);
	}

	private static final int wordSize = 4;
	private int offset = 0;
	static final Temp FP = new Temp(); // virtual frame pointer
	private static final Label badPtr = new Label("BADPTR");
	private static final Label badSub = new Label("BADSUB");
	int maxArgOffset = 0;
	private static boolean spilling = false;

	// set spilling to true when the spill method is implemented

	@Override
	public Temp FP() {
		return FP;
	}

	@Override
	public Temp RV() {
		return V0;
	}

	public Temp R0() {
		return ZERO;
	}

	@Override
	public int wordSize() {
		return wordSize;
	}

	public Temp[] registers() {
		return registers;
	}

	public Label badPtr() {
		return badPtr;
	}

	public Label badSub() {
		return badSub;
	}

	public String tempMap(Temp temp) {
		return tempMap.get(temp);
	}

	private static HashMap<String, Integer> functions = new HashMap<String, Integer>();
	private static HashMap<String, Label> labels = new HashMap<String, Label>();

	/* Construtor vazio */
	public MipsFrame() {
	}

	/*
	 * Criação de um novo frame
	 *     Label (nome) do novo frame, e a lista de formais
	 */
	@Override
	public Frame newFrame(Label name, BoolList formals) {
		super.name = name;
		BoolList boolean_iterator = formals;
		AccessList formals_iterator = super.formals;
		while (boolean_iterator.tail != null) {
			formals_iterator.head = allocLocal(boolean_iterator.head);
			formals_iterator = formals_iterator.tail;
			boolean_iterator = boolean_iterator.tail;
		}

		return null;
	}

	/*
	 * Dependendo da variavel escape, a alocacao deve ser no frame ou no registro
	 * Caso a variavel escape, entao sera no frame, sera criado um objeto InFrame
	 * com o argumento do offset atual (funciona como se fosse o local de memoria),
	 * Caso nao escape, entao é criado um InReg com o registrador onde a variavel
	 * sera guardada
	 */
	public Access allocLocal(boolean escape) {
		if (escape) {
			Access result = new InFrame(offset);
			offset -= wordSize;
			return result;
		} else
			return new InReg(new Temp());
	}

	@Override
	public Exp externalCall(String s, ExpList expl) {

		String func = s.intern();
		Label l = labels.get(func);
		if (l == null) {
			l = new Label("_" + func);
			labels.put(func, l);
		}

		return new CALL(new NAME(l), expl);
	}

	private static <R> void addAll(java.util.Collection<R> c, R[] a) {
		for (int i = 0; i < a.length; i++)
			c.add(a[i]);
	}

	private static Stm SEQ(Stm left, Stm right) {
		if (left == null)
			return right;
		if (right == null)
			return left;
		return new SEQ(left, right);
	}

	private static MOVE MOVE(Exp d, Exp s) {
		return new MOVE(d, s);
	}

	private static TEMP TEMP(Temp t) {
		return new TEMP(t);
	}

	private void assignFormals(AccessList formals, Iterator<Access> actuals, Stm body) {
		Access formal = formals.head;
		Access actual = actuals.next();
		assignFormals(formals, actuals, body);
		MOVE(formal.exp(TEMP(FP)), actual.exp(TEMP(FP)));
	}

	private void assignCallees(int i, Stm body) {
		if (i >= calleeSaves.length)
			return;
		Access a = allocLocal(!spilling);
		assignCallees(i + 1, body);
		MOVE(a.exp(TEMP(FP)), TEMP(calleeSaves[i]));
		MOVE(TEMP(calleeSaves[i]), a.exp(TEMP(FP)));
	}

	private List<Access> actuals;

	public void procEntryExit1(Stm body) {
		assignFormals(formals, actuals.iterator(), body);
		assignCallees(0, body);
	}

	/*
	*/

	//Mini Java Library will be appended to end of
	//program
	public String programTail() {

		return "         .text            \n" + "         .globl _halloc   \n"
				+ "_halloc:                  \n" + "         li $v0, 9        \n"
				+ "         syscall          \n" + "         j $ra            \n"
				+ "                          \n" + "         .text            \n"
				+ "         .globl _printint \n" + "_printint:                \n"
				+ "         li $v0, 1        \n" + "         syscall          \n"
				+ "         la $a0, newl     \n" + "         li $v0, 4        \n"
				+ "         syscall          \n" + "         j $ra            \n"
				+ "                          \n" + "         .data            \n"
				+ "         .align   0       \n" + "newl:    .asciiz \"\\n\"  \n"
				+ "         .data            \n" + "         .align   0       \n"
				+ "str_er:  .asciiz \" ERROR: abnormal termination\\n\" "
				+ "                          \n" + "         .text            \n"
				+ "         .globl _error    \n" + "_error:                   \n"
				+ "         li $v0, 4        \n" + "         la $a0, str_er   \n"
				+ "         syscall          \n" + "         li $v0, 10       \n"
				+ "         syscall          \n";
	}

}
